import hashlib
import logging
import re
import sqlalchemy as sql
import time
import urllib
import yotsuba

from pylons import config, request, response, session, tmpl_context as c
from pylons.controllers.util import abort
try:
    from pylons.controllers.util import redirect_to
except:
    from pylons.controllers.util import redirect as redirect_to

from serverengine.lib import helpers
from serverengine.lib import storage
from serverengine.lib.base import BaseController, render
from serverengine.model.meta import Session, TUser, table_users

log = logging.getLogger(__name__)

class UsersController(BaseController):
    """REST Controller styled on the Atom Publishing Protocol"""
    # To properly map this controller, ensure your config/routing.py
    # file has a resource setup:
    #     map.resource('user', 'users')
    
    def __init__(self):
        self.userdb = Session.query(TUser)
    
    def __before__(self):
        c.app_name = config['app_name']
        c.app_vers = config['app_vers']
        c.app_base = config['app_base']
        c.isLoggedIn = helpers.hasAuthorization()
        c.isAdminAccess = helpers.hasAuthorization(True)
        if c.isLoggedIn:
            c.sessionExpirationTime = time.strftime("%Y.%m.%d %H:%M:%S %Z", time.localtime(session['length']))
    
    def index(self, format='html'):
        """GET /users: All items in the collection"""
        # If this session is authenticated as admin, then redirect to the login page
        if not c.isAdminAccess:
            redirect_to(c.app_base)
        else:
            c.quota = int(config['quotasize'])
            c.app_name = config['app_name']
            c.app_vers = config['app_vers']
            c.users = []
            c.q = ''
            if 'q' in request.params:
                c.q = request.params['q'].encode('utf-8', 'ignore')
                session['q'] = c.q.strip()
                session.save()
            if 'q' in session and len(session['q']) > 0:
                c.users = self.userdb.filter(
                    sql.or_(
                        TUser.username.like("%" + c.q + "%"),
                        TUser.realname.like("%" + c.q + "%"),
                        TUser.email.like("%" + c.q + "%")
                    )
                ).all()
            else:
                c.users = self.userdb.all()
            c.userUsageStat = {}
            for user in c.users:
                sizePath = storage.getUserPath(user.username)
                usedSize = int(storage.getDirSize(sizePath))
                c.userUsageStat[user.username] = "%.2f" % (100 - usedSize * 100.0 / c.quota)
            return render('interface/users.list.html')

    def create(self):
        """POST /users: Create a new item"""
        # List of required inputs
        requiredInputKeys = ['name', 'email', 'username', 'password']
        requiredInputLabels = ['real name', 'email address', 'user name', 'password']
        requiredInputs = dict(zip(requiredInputKeys, requiredInputLabels))
        
        # Response Code
        responseCode = 200
        responseReason = "OK"
        
        # Regular Expressions
        validEmailSyntax = re.compile("^[\w0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+(\.[\w0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+)*\@[a-z0-9\-\+\_]+(\.[a-z0-9\-\+\_]+)*(\.[a-z]+)+$")
        validUsername = re.compile("^[a-z0-9-_\.]+$", re.I)
        
        # Verify inputs
        for key in requiredInputKeys:
            log.debug("%s: %s" % (key, request.params[key]))
            if key not in request.params or len(unicode(request.params[key]).strip()) == 0:
                responseCode = 400
                responseReason = "Please provide your %s" % requiredInputs[key]
                break
            elif key == 'email' and not validEmailSyntax.match(request.params[key]):
                responseCode = 400
                responseReason = "Invalid e-mail address"
                break
            elif key == 'username' and not validUsername.match(request.params[key]):
                responseCode = 400
                responseReason = ' '.join([
                    "Invalid username. The username should contains only the",
                    "combination of English alphabets, digits, dot (.), plus (+),",
                    "underscore (_) or hyphen (-)."
                ])
                break
            elif key == 'password' and not request.params[key] == request.params['verpass']:
                responseCode = 400
                responseReason = "Password unmatched"
                break
        
        if responseCode == 400:
            response.status = "%d %s" % (responseCode, responseReason)
            log.info(response.status)
            return responseReason
        
        if re.match(u"^%s$" % config['admin_username'], request.params['username'], re.I):
            responseCode = 403
            responseReason = "This username is in use."
        
        if True:#try:
            feedback = helpers.addUser(
                username = request.params['username'],
                password = request.params['password'],
                realname = request.params['name'],
                email = request.params['email']
            )
            
            if feedback is None:
                responseCode = 200
                responseReason = "OK"
            else:
                responseCode = 403
                responseReason = feedback
        else:#except:
            responseCode = 500
            responseReason = "Encountered technical problem"
        
        response.status = "%d %s" % (responseCode, responseReason)
        log.info(response.status)
        return responseReason

    def new(self, format='html'):
        """GET /users/new: Form to create a new item"""
        # url('new_user')

    def update(self, id):
        """PUT /users/id: Update an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="PUT" />
        # Or using helpers:
        #    h.form(url('user', id=ID),
        #           method='put')
        # url('user', id=ID)
        setOfInputs = {
            'profile': {
                'isOK': True,
                'list': ['realname']
            },
            'password': {
                'isOK': True,
                'list': ['password_new', 'password_ver']
            }
        }
        failedMessages = {
            'emptyInput': 'Please provide information.',
            'notMatchedNewPasswords': 'Passwords do not match.'
        }
        originalScore = len(setOfInputs)
        executeSQL = True
        # Check for emptiness
        for dataset, setBlock in setOfInputs.iteritems():
            for inputKey in setBlock['list']:
                if inputKey not in request.params or len(request.params[inputKey].strip()) == 0:
                    setOfInputs[dataset]['isOK'] = False
        score = originalScore
        for dataset, setBlock in setOfInputs.iteritems():
            if not setBlock['isOK']:
                score -= 1
        if score <= 0:
            self.__setFeedback(failedMessages['emptyInput'])
            executeSQL = False
            #abort(400)
        
        responseContent = ''
        
        # Check for exact matching
        if setOfInputs['password']['isOK'] \
           and not request.params[setOfInputs['password']['list'][0]] == request.params[setOfInputs['password']['list'][1]]:
            self.__setFeedback(failedMessages['notMatchedNewPasswords'])
            executeSQL = False
            #abort(400)
        
        if executeSQL:
            # Update profile
            if setOfInputs['profile']['isOK']:
                Session.execute(table_users.update().where(table_users.c.username==id).values(realname = request.params[setOfInputs['profile']['list'][0]]))
                self.__resetFeedback()
            
            # Update password
            if setOfInputs['password']['isOK']:
                Session.execute(table_users.update().where(table_users.c.username==id).values(password = helpers.encodePassword(request.params[setOfInputs['password']['list'][0]])))
                self.__resetFeedback()
            
            user = self.userdb.filter(TUser.username == id).first()
            
            if user is None:
                abort(500)
            
            helpers.cache_user_object(user)
            
            session['user'] = user.dict()
            session.save()
            helpers.extendSessionLength(False)
            
            responseContent = self.show(id)
        else:
            log.info("Bad input")
            responseContent = self.edit(id)
            
        return responseContent
        
        #http://www.sqlalchemy.org/docs/05/sqlexpression.html#updates
        #conn.execute(users.update().where(TUsers.c.name==id).values(name='...')) 

    def delete(self, id):
        """DELETE /users/id: Delete an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="DELETE" />
        # Or using helpers:
        #    h.form(url('user', id=ID),
        #           method='delete')
        # url('user', id=ID)
        log.debug("Access to user profile (%s)" % id)
        user = self.userdb.filter(TUser.username == id).first()
        
        if user is None:
            abort(404)
        
        # Security Check
        c.isOwner = 'user' in session and session['user']['username'] == user.username
        
        # Initialize data
        c.q = ''
        if 'q' in session:
            c.q = '?q=%s' % session['q']
        
        if c.isAdminAccess:
            Session.delete(user)
            if self.userdb.filter(TUser.username == id).count() > 0:
                abort(500)
            redirect_to("%s/users" % c.app_base)
        if c.isOwner:
            abort(405)
        else:
            abort(403)

    def show(self, id, format='html'):
        """GET /users/id: Show a specific item"""
        # url('user', id=ID)
        log.debug("Access to user profile (%s)" % id)
        user = self.userdb.filter(TUser.username == id).first()
        
        if user is None:
            abort(404)
        
        # Security Check
        c.isOwner = 'user' in session and session['user']['username'] == user.username
        c.isEditable = c.isAdminAccess or (c.isLoggedIn and c.isOwner)
        
        # Initialize data
        c.q = ''
        if 'q' in session:
            c.q = '?q=%s' % session['q']
        c.userUsername = user.username
        c.userRealname = user.realname
        c.userEmail = helpers.consealEmailAddress(user.email)
        return render('interface/users.profile.overview.html')

    def edit(self, id, format='html'):
        """GET /users/id/edit: Form to edit an existing item"""
        # url('edit_user', id=ID)
        log.debug("Access to user profile (%s) for editing" % id)
        user = self.userdb.filter(TUser.username == id).first()
        
        if user is None:
            abort(404)
        
        # Security Check
        c.isOwner = 'user' in session and session['user']['username'] == user.username
        c.isEditable = c.isAdminAccess or (c.isLoggedIn and c.isOwner)
        
        if not c.isEditable:
            abort(403)
        
        c.q = ''
        if 'q' in session:
            c.q = '?q=%s' % session['q']
        c.userUsername = user.username
        c.userRealname = user.realname
        c.userEmail = user.email
        c.feedback = self.__getFeedback()
        return render('interface/users.profile.edit.html')
    
    def __setFeedback(self, message):
        session['user_feedback'] = message
        session.save()
    
    def __resetFeedback(self):
        try:
            del session['user_feedback']
            session.save()
        except:
            pass
    
    def __getFeedback(self):
        feedback = ''
        if 'user_feedback' in session:
            log.debug("Feedback / Orig: %s" % session['user_feedback'])
            
            feedback = '%s' % session['user_feedback']
            self.__resetFeedback()
            
            log.debug("Feedback / Copy: %s" % feedback)
        else:
            log.debug("Feedback / Empty")
            
        return feedback